Add agent tool to manually regenerate a corpus icon#1938
Conversation
Expose the corpus logo generator (previously only run once at corpus creation by the auto-branding flow) as an on-demand agent tool so a user can ask the corpus chat agent to (re)generate a collection's icon at any time. - New tool aregenerate_corpus_icon in opencontractserver/llms/tools/core_tools/corpus_branding.py: creator-only, approval-gated, corpus-scoped. Optional additional_instructions styling hint (sanitised + length-capped) is folded into the image prompt. - New public helper aregenerate_corpus_logo in opencontractserver/corpuses/services/branding.py shares the auto-branding primitives (_build_logo_prompt + agenerate_logo_image) and persists via the creator-gated CorpusService.update_icon. Unlike the create-time path it deliberately overwrites an existing icon and ignores auto_branding_enabled. - _build_logo_prompt gains an optional additional_instructions argument. - Registered in tool_registry (alias generate_corpus_icon) and wired into the interactive corpus agent's authenticated toolset alongside update_corpus_description. - Constant CORPUS_LOGO_ADDITIONAL_INSTRUCTIONS_MAX_CHARS. - Tests in opencontractserver/tests/test_corpus_icon_tool.py.
Code Review — PR #1938: Add agent tool to manually regenerate a corpus iconOverviewThis PR adds a well-scoped Issues1.
|
- Import the concrete opencontractserver.users.models.User model class for the creator/other type annotations instead of the get_user_model() runtime variable, which mypy rejects as a type (valid-type) and pyupgrade unquotes under 'from __future__ import annotations'. Matches test_caml_review_tools. - Guard FieldFile.name (str | None) and AsyncMock.await_args (Call | None) before attribute access; mark the intentional anonymous user_id=None call with type: ignore[arg-type].
Code Review — Add agent tool to manually regenerate a corpus iconOverviewThis PR exposes the existing corpus auto-branding generator as an on-demand agent tool ( SecurityPrompt injection defence is solid. IDOR-safe design is correct. Both "corpus not found" and "corpus not visible to user" hit the same Defence-in-depth on the creator gate (checked up front AND re-enforced by Potential Bugs1. @pytest.mark.asyncio
async def test_requires_approval_fires(self):
2. "additional_instructions_applied": bool(
additional_instructions and additional_instructions.strip()
),This checks whether the input was non-blank, not whether the sanitized hint actually made it into the prompt. A value consisting entirely of quotes (e.g. Code Quality / Style3. Constant deferred-imported inside from opencontractserver.constants.corpus_branding import (
CORPUS_LOGO_ADDITIONAL_INSTRUCTIONS_MAX_CHARS,
)
4. PR footer violates project policy (PR description, last line)
PerformanceThe pre-generation permission check (before the up-to-90s image round-trip) is a well-considered optimisation. The Test CoverageCoverage is good: prompt builder edge cases (blank, quote injection, length cap), happy path (real monogram fallback, icon replacement, hint propagation into prompt), and all denial branches (anonymous, no-access, read-only collaborator, missing corpus, approval gate). The mock target ( One coverage note: SummaryTwo items worth addressing before merge:
The core implementation (branding helper, tool, registry wiring, permission model) is clean and follows project conventions. Nice work on the prompt-injection hardening and defence-in-depth on the creator gate. |
Place the concrete-User-model import in alphabetical order (between types.enums and utils.*) as a bare import — the standalone explanatory comment block sat out of order. Moved the rationale to a comment beside the creator/other annotations instead.
Code Review: Add agent tool to manually regenerate a corpus iconOverviewThis PR exposes the existing corpus logo generator as an on-demand agent tool so a user can ask the corpus chat agent to regenerate the collection icon at any time. It is a well-scoped, single-purpose addition that reuses existing infrastructure cleanly. Strengths
Issues1. CLAUDE.md violation: PR body credits Claude CodeRule: "Never credit Claude or Claude Code in commit messages, PR messages, comments, or any other artifacts." The PR description ends with 2.
|
- branding.py: extract sanitize_logo_instruction_hint() and use it in _build_logo_prompt so the "hint applied?" answer derives from the sanitised value in one place. - corpus_branding tool: report additional_instructions_applied from the sanitised hint (a value that collapses to empty — e.g. only quotes — no longer falsely reports as applied); annotate injected user_id as int | None to match the existing None guard and drop the now-stale test type: ignore. - test_corpus_icon_tool.py: hoist the PIL import to module level, add an explicit settings.CORPUS_LOGO_GENERATION_ENABLED guard on the real-fallback test, and add a creator-only denial test for a collaborator holding a guardian UPDATE grant. Kept the @pytest.mark.asyncio + class-level @pytest.mark.django_db markers: they mirror test_image_tools.py, where the marker grants the async TransactionTestCase method DB access under pytest-asyncio.
Code Review — PR #1938: Add agent tool to manually regenerate a corpus iconOverall this is a well-structured, thoughtful PR. The intent is clear, the permissioning story is solid, and the test suite is thorough. A few items warrant attention before merge. Issues1. Inline
|
Summary
Today the corpus icon/logo generator only runs once — automatically, at corpus-creation time, via the auto-branding flow (
opencontractserver/corpuses/services/branding.py). This PR exposes that same generator as an on-demand agent tool so a user can ask the corpus chat agent to (re)generate a collection's icon at any point ("regenerate the icon", "give it a logo featuring a set of scales").What's included
regenerate_corpus_icon(opencontractserver/llms/tools/core_tools/corpus_branding.py)update_corpus_description/CorpusService.update_icon). The creator gate is checked up front (so we never spend an image-generation round-trip on a request that can't persist) and re-enforced byupdate_icon.corpus_id/user_idare framework-injected (hidden from the LLM); the only LLM-visible argument is an optionaladditional_instructionsstyling hint, which is sanitised + length-capped before being folded into the image prompt.aregenerate_corpus_logo(branding.py) shares the auto-branding primitives (_build_logo_prompt+agenerate_logo_image, with the deterministic PIL monogram fallback) and persists through the creator-gatedCorpusService.update_icon. Unlike the create-time path it deliberately overwrites an existing icon and ignoresauto_branding_enabled— a manual regeneration is an explicit request, not the best-effort create-time default. Corpus/user rows are re-loaded inside the save'sdatabase_sync_to_asyncboundary (mirroring_generate_logo)._build_logo_promptgains an optionaladditional_instructionsargument (auto-branding path passesNone→ byte-identical behaviour).tool_registry.py(aliasgenerate_corpus_icon;requires_approval+requires_corpus+requires_write_permission) and wired into the interactive corpus agent's authenticated toolset (pydantic_ai_agents.py) alongsideupdate_corpus_description.CORPUS_LOGO_ADDITIONAL_INSTRUCTIONS_MAX_CHARS.Permissioning
update_description/update_icon.<user_content>fence).Tests
opencontractserver/tests/test_corpus_icon_tool.pycovers: the prompt builder's new hint handling (inclusion, blank-skip, quote neutralisation, length cap); the tool end-to-end (real monogram fallback persists a PNG, replaces an existing icon, the hint reaches the prompt); permission denials (anonymous, no-access, READ-only-but-not-creator, missing corpus); and registry wiring (flags, alias, approval gate firing through the wrapper).Changelog:
changelog.d/regenerate-corpus-icon-tool.added.md.